package org.hilo.boot.app.config;

import java.net.UnknownHostException;

import org.hilo.boot.core.redis.LettuceConnectionConfiguration;
import org.hilo.boot.core.redis.RedisConnectionConfiguration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import io.lettuce.core.RedisClient;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.RefreshTrigger;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;

/**
 * 
 * @author zollty
 * @since 2022年8月22日
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "hilo.enable-default-redis", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(name = "redisConnectionFactory")
class DefaultLettuceConnectionFactory extends RedisConnectionConfiguration {

    /**
     * @param properties
     * @param sentinelConfigurationProvider
     * @param clusterConfigurationProvider
     */
    protected DefaultLettuceConnectionFactory(RedisProperties properties,
            ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
            ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
        super(properties, sentinelConfigurationProvider.getIfAvailable(),
                clusterConfigurationProvider.getIfAvailable());
    }

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(ClientResources.class)
    public DefaultClientResources lettuceClientResources() {
        return DefaultClientResources.create();
    }

    /**
     * 解决问题： 节点异常，服务端的Redis集群拓扑被刷新了，但是本地没有获取到新的拓扑。 现象：redis
     * cluster集群有一台机崩了之后，后台服务的redis会一直报错，无法连接到redis集群（RedisCommandTimeoutException）。
     * 
     * lettuce客户端支持的 自适应拓扑刷新（Adaptive updates）与定时拓扑刷新（Periodic updates） 是默认关闭的。
     * 
     * see https://github.com/lettuce-io/lettuce-core/wiki/Redis-Cluster see
     * https://lettuce.io/core/snapshot/reference/#client-options
     */
    @Bean
    @ConditionalOnProperty(name = "hilo.redis.lettuce.enable-topology-refresh", havingValue = "true", matchIfMissing = true)
    public LettuceClientConfigurationBuilderCustomizer lettuceClientCustomizer() {
        return new LettuceClientConfigurationBuilderCustomizer() {
            @Override
            public void customize(LettuceClientConfigurationBuilder clientConfigurationBuilder) {

                ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions
                        .builder()
                        // 周期性刷新
                        // .enablePeriodicRefresh()
                        // 事件触发性刷新（常见三种情况：收到-ASK、-MOVED指令 或者 物理重连接，其中）
                        // By default, commands follow -ASK and -MOVED redirects up to 5 times until the command
                        // execution is considered to be failed
                        .enableAdaptiveRefreshTrigger(RefreshTrigger.MOVED_REDIRECT,
                                RefreshTrigger.PERSISTENT_RECONNECTS)
                        .build();

                clientConfigurationBuilder.clientOptions(ClusterClientOptions.builder()
                        .topologyRefreshOptions(topologyRefreshOptions).build());
            }
        };
    }

    @Primary
    @Bean
    public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources,
            ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers)
            throws UnknownHostException {
        return new LettuceConnectionConfiguration(getProperties(), getSentinelConfig(),
                getClusterConfiguration(), builderCustomizers)
                        .redisConnectionFactory(clientResources);
    }

}
